/**
 * Copyright (C) 2009 Matteo Merli <mm@merlimat.org>
 * 
 * $Id: GlobalScope.java 24 2009-01-19 19:49:05Z matteo.merli $
 * $URL: http://pxl-lang.googlecode.com/svn/trunk/src/main/java/pxl/GlobalScope.java $
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *         http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package pxl;

import java.lang.reflect.Method;

import pxl.errors.TypeError;
import pxl.errors.ValueError;
import pxl.types.JavaMethod;
import pxl.types.PxlBoolean;
import pxl.types.PxlFile;
import pxl.types.PxlFloat;
import pxl.types.PxlInteger;
import pxl.types.PxlList;
import pxl.types.PxlMap;
import pxl.types.PxlObject;
import pxl.types.PxlRange;
import pxl.types.PxlString;
import pxl.types.PxlTuple;
import pxl.types.annotations.Exported;
import pxl.types.annotations.Parameter;
import pxl.types.annotations.Parameters;

import com.google.common.base.Join;

/**
 * @author Matteo Merli
 */
public final class GlobalScope extends Scope
{

    public static Scope getInstance()
    {
        return globalScope;
    }

    /**
     * Private constructor: the global scope is unique in the application.
     */
    private GlobalScope()
    {
        super( "GlobalScope", null );

        Method[] methods = GlobalScope.class.getMethods();
        for ( Method method : methods )
        {
            if ( method.isAnnotationPresent( Exported.class ) )
            {
                String name = method.getAnnotation( Exported.class ).name();
                if ( name.length() == 0 )
                    name = method.getName();
                put( name, new JavaMethod( method ) );
            }
        }
    }

    // / Built-in functions

    @Exported
    public static PxlObject abs( PxlObject obj )
    {
        return obj.__abs__();
    }

    @Exported
    public static PxlObject str( PxlObject obj )
    {
        return obj.__str__();
    }

    @Exported
    public static PxlObject repr( PxlObject obj )
    {
        return obj.__repr__();
    }

    @Exported
    public static PxlObject map()
    {
        return new PxlMap();
    }

    @Exported
    public static PxlList list()
    {
        return new PxlList();
    }

    @Exported
    public static PxlObject max( PxlObject a, PxlObject b )
    {
        if ( a.__gt__( b ) == PxlBoolean.True )
            return a;
        else
            return b;
    }

    @Exported
    @Parameters ( parameters = { // 
    @Parameter ( name = "x", optional = true) })
    public static PxlBoolean bool( PxlObject x )
    {
        if ( x == null )
            return PxlBoolean.False;
        else
            return x.__bool__();
    }

    @Exported
    public static PxlString type( PxlObject obj )
    {
        return new PxlString( obj.getType() );
    }

    private static PxlString pxlSep = new PxlString( "sep" );
    private static PxlString pxlEnd = new PxlString( "end" );

    @Exported
    @Parameters ( parameters = { // 
    @Parameter ( name = "items", isTuple = true),
            @Parameter ( name = "keys", isMap = true) })
    public static void print( PxlTuple items, PxlMap keys )
    {
        String sep = " ";
        String end = "\n";

        if ( keys.getValue().containsKey( pxlSep ) )
        {
            sep = keys.getValue().get( pxlSep ).toString();
        }

        if ( keys.getValue().containsKey( pxlEnd ) )
        {
            end = keys.getValue().get( pxlEnd ).toString();
        }

        StringBuilder sb = new StringBuilder();
        Join.join( sb, sep, items.getValue() );
        sb.append( end );
        System.out.print( sb.toString() );
        System.out.flush();
    }

    @Exported
    @Parameters ( parameters = { // 
    @Parameter ( name = "obj", optional = true) })
    public static PxlObject dir( PxlObject obj )
    {
        if ( obj != null )
            return obj.__dir__();

        Scope scope = Context.getInstance().getCurrentScope();
        PxlList list = new PxlList();
        for ( String symbol : scope.getSymbols() )
            list.add( new PxlString( symbol ) );

        return list;
    }

    @Exported
    public static PxlTuple divmod( PxlObject a, PxlObject b )
    {
        return a.__divmod__( b );
    }

    @Exported
    public static PxlObject hash( PxlObject obj )
    {
        return new PxlInteger( obj.hashCode() );
    }

    @Exported
    public static PxlList range( PxlObject end )
    {
        if ( !(end instanceof PxlInteger) )
            throw new TypeError( "range bounds must be integers" );

        PxlList list = new PxlList();
        int n = (int) ((PxlInteger) end).getValue();
        for ( int i = 0; i < n; i++ )
            list.add( new PxlInteger( i ) );

        return list;
    }

    @Exported
    public static PxlRange xrange( PxlObject end )
    {
        if ( !(end instanceof PxlInteger) )
            throw new TypeError( "range bounds must be integers" );

        int n = (int) ((PxlInteger) end).getValue();
        return new PxlRange( n );
    }

    @Exported
    public static PxlString hex( PxlObject obj )
    {
        if ( obj instanceof PxlInteger )
        {
            PxlInteger n = (PxlInteger) obj;
            return n.toHexString();
        }

        throw new TypeError( "'" + obj.getType()
                + "' object cannot be interpreted as an integer" );
    }

    @Exported
    public static PxlString oct( PxlObject obj )
    {
        if ( obj instanceof PxlInteger )
        {
            PxlInteger n = (PxlInteger) obj;
            return n.toOctalString();
        }

        throw new TypeError( "'" + obj.getType()
                + "' object cannot be interpreted as an integer" );
    }

    @Exported
    public static PxlString bin( PxlObject obj )
    {
        if ( obj instanceof PxlInteger )
        {
            PxlInteger n = (PxlInteger) obj;
            return n.toBinaryString();
        }

        throw new TypeError( "'" + obj.getType()
                + "' object cannot be interpreted as an integer" );
    }

    @Exported ( name = "int")
    @Parameters ( parameters = { // 
    @Parameter ( name = "x"), // 
            @Parameter ( name = "base", optional = true) })
    public static PxlObject _int( PxlObject x, PxlObject base )
    {
        if ( x instanceof PxlString )
        {
            int iBase = 10;

            if ( base != null )
            {
                if ( !(base instanceof PxlInteger) )
                    throw new TypeError( "an integer is required for the base" );

                iBase = (int) ((PxlInteger) base).getValue();
            }

            String s = ((PxlString) x).getValue();

            switch ( iBase )
            {
            case 16:
                if ( s.startsWith( "0x" ) )
                    s = s.substring( 2 );
                break;
            case 8:
                if ( s.startsWith( "0o" ) )
                    s = s.substring( 2 );
                break;
            case 2:
                if ( s.startsWith( "0b" ) )
                    s = s.substring( 2 );
                break;
            default:
                break;
            }

            long value;

            try
            {
                value = Long.parseLong( s, iBase );
            }
            catch ( NumberFormatException e )
            {
                throw new ValueError( "invalid literal for int() with base "
                        + iBase + ": '" + x + "'" );
            }
            return new PxlInteger( value );
        }
        else
            return x.__int__();
    }

    @Exported ( name = "float")
    public static PxlObject _float( PxlObject x )
    {
        if ( x instanceof PxlString )
        {
            String s = ((PxlString) x).getValue();
            try
            {
                return new PxlFloat( Double.parseDouble( s ) );
            }
            catch ( NumberFormatException e )
            {
                throw new ValueError( "invalid literal for float() : '" + x
                        + "'" );
            }
        }
        else
            return x.__float__();
    }

    @Exported
    public static PxlFile file( PxlObject fileName )
    {
        if ( !(fileName instanceof PxlString) )
            throw new TypeError( "File name must be a string" );

        return new PxlFile( ((PxlString) fileName).getValue() );
    }

    @Exported
    public static PxlInteger len( PxlObject obj )
    {
        return obj.__len__();
    }

    private static final GlobalScope globalScope = new GlobalScope();

}
